﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Web;
using System.Net;
using System.IO;
using Microsoft.WindowsLive.Id.Client;
using Campari.Software.Networking;

namespace LiveIDSkyDriveApp
{
    public partial class frmMain : Form
    {
        #region Global Variables
        string skydriveurl = "http://cid-{0}.skydrive.live.com/";
        string skydrivehome = "https://cid-{0}.skydrive.live.com/home.aspx";
        string cid = "";
        string user = "";

        IdentityManager idmgr;
        Identity oID;
        TreeNode root;
        TreeNode title;
        Regex r;
        BackgroundWorker getFolders;
        #endregion

        public frmMain()
        {
            InitializeComponent();

            getFolders = new BackgroundWorker();
            getFolders.WorkerReportsProgress = false;
            getFolders.WorkerSupportsCancellation = false;
            getFolders.RunWorkerCompleted += new RunWorkerCompletedEventHandler(getFolders_RunWorkerCompleted);
            getFolders.DoWork += new DoWorkEventHandler(getFolders_DoWork);

            try
            {
                // Sets the variables that will appear in the Live ID window
                idmgr = IdentityManager.CreateInstance("ScottIsAFool;wlm@scottisafool.co.uk;Scøtt's Simple SkyDrive App",
                    "Scøtt's Simple SkyDrive App");
                oID = idmgr.CreateIdentity();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to initialise for the following reason: " + ex.Message, "Big Fat Hairy Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            lblInfo.Text = "Not signed in";
            lblAdding.Text = "";
            btnSignIn.Text = "&Sign in";

            // Adds a root to the treeview
            root = treeView1.Nodes.Add("SkyDrive");
            root.Name = "http://skydrive.live.com/";
        }

        void getFolders_DoWork(object sender, DoWorkEventArgs e)
        {
            Invoke(new ShowHide(ShowHideSpinner), true);
            string link = string.Format(skydrivehome, cid);
            string t = GetSource(link);
            List<string> a = new List<string>();

            // this is a regular expression to check for the urls 
            r = new Regex("href\\s*=\\s*(?:(?:\\\"(?<url>[^\\\"]*)\\\")|(?<url>[^\\s]* ))");
            // get all the matches depending upon the regular expression
            MatchCollection mcl = r.Matches(t);

            // In the source code is a span tag that has the user's display name, this regex gets that
            Match m = Regex.Match(t, "<span id=\"sdx_crd_name\">(.*?)</span>");
            if (m.Success)
                user = HttpUtility.HtmlDecode(m.Groups[1].Value); // Gets rid of any crap in the name

            foreach (Match ml in mcl)
            {
                // This loop basically gets all the folder links that are at the root level of the user's SkyDrive account
                foreach (Group g in ml.Groups)
                {
                    string b = g.Value + "";

                    // Checks the links are publicly available, if they are, they will start http, not https
                    // Add the extracted urls to the array list
                    if (b.StartsWith("http://cid-"))
                        a.Add(b);
                }
            }
            // See if there are any public folders.
            if (a.Count == 0)
            {
                MessageBox.Show("Sorry, you do not have any public folders to show", "Doh", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                // Create the node that has the user's name in it, all public folders will appear under this node.
                title = new TreeNode(user);
                title.Name = link;

                // Add the node to the root ("SkyDrive")
                Invoke(new AddToTree(AddToTreeFunction), title, root);
                Invoke(new ChangeAdding(ChangeAddingFile), "user: " + user);
                string p = "";

                // Now we cycle through the list of public folders and add all sub folders and files to the tree
                foreach (string n in a)
                {
                    p = HttpUtility.UrlDecode(n.Substring(n.IndexOf("browse.aspx") + 12), Encoding.ASCII);

                    // Creates the node for the top most folders
                    TreeNode node = new TreeNode(p);
                    node.Name = n;
                    node.ToolTipText = p;

                    // Adds the new node to the user's node
                    Invoke(new AddToTree(AddToTreeFunction), node, title);
                    Invoke(new ChangeAdding(ChangeAddingFile), "folder: " + p);

                    // Sends across the newly created node, plus the folder's link to get all subfolders and files added.
                    AddNode(n, node);

                }
                Invoke(new Expand(ExpandTrees));
            }
        }

        void getFolders_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Invoke(new ShowHide(ShowHideSpinner), false);          
        }

        /// <summary>
        /// Handles the Click event of the button1 control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnCheck_Click(object sender, EventArgs e)
        {
            // Checks whether we're signed in
            if (oID.IsAuthenticated)
            {
                if (getFolders.IsBusy)
                {
                    MessageBox.Show("There is a check in progress, please wait.", "Doh", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                // Clears the initial text that displays what files are being added to the treeview
                lblAdding.Text = "";

                // Clears the tree view, leaving only the root node showing.
                root.Nodes.Clear();

                // Start the process of getting the files and folders
                getFolders.RunWorkerAsync();

            }
            else // We're not signed in
                MessageBox.Show("Sorry, you're not signed in, please sign in.");
        }

        /// <summary>
        /// Adds a new node.
        /// </summary>
        /// <param name="link">The link.</param>
        /// <param name="parentnode">The parentnode.</param>
        private void AddNode(string link, TreeNode parentnode)
        {
            // Set the root skydrive link for that user
            string skydrive = string.Format(skydriveurl, cid);
            string name = "";

            // Get the source for the link we've been passed
            string source = GetSource(link);

            // Decode the link to get rid of the crap that might be there
            link = HttpUtility.UrlDecode(link, Encoding.ASCII);

            // Set the name to be what ever appears after the browse part of the link,
            // this will be used to get just the file or folder name of the new entry
            name = link.Substring(link.IndexOf("browse.aspx") + 12);

            bool filedone = false;

            // Get a list of all the links in that link's source code
            MatchCollection mc = r.Matches(source);
            foreach (Match ml in mc)
            {
                foreach (Group g in ml.Groups)
                {
                    // Decode the link, gets rid of any crap in it.
                    string b = HttpUtility.UrlDecode(g.Value + "", Encoding.ASCII);
                    // Only links starting with http://cid- are links that we are interested in
                    // links starting with https://cid- aren't public folders or files, so we want to
                    // ignore them.

                    if (b.Contains("livefilestore.com") && !filedone)
                    {
                        if (b.Contains("?download"))
                            parentnode.Name = b.Substring(6, b.IndexOf("?download") + 2);
                        else
                            parentnode.Name = b.Substring(6, b.Length - 7);
                        filedone = true;
                    }
                    else if (b.StartsWith("http://cid-") && link.Contains("browse.aspx"))
                    {
                        // This if statement checks for particular types of links. In the source, we will
                        // sometimes get a self.aspx page that points to the folder we're already browsing
                        // so we don't want to be adding that.
                        if ((b.Contains("self.aspx") || b.Contains("browse.aspx")) && // makes sure the link is either a folder or file link
                            (b != string.Format("{0}self.aspx/{1}", skydrive, name) && // Checks that the link is not a self.aspx link to the folder we're browsing
                            b != string.Format("{0}self.aspx/{1}/", skydrive, name)) && // Checks the same again but with a trailing slash
                            (b != link || b != link + "/") && !b.EndsWith("/") && // Checks that the new link is not the same as the original link passed to the function
                            !b.EndsWith(parentnode.Name) && // Checks that the new link doesn't end with the name of the parent node, in other words, its the effectively the same link
                            !link.Contains(b)) // Checks that the new link is not already contained in the original link, if it is, we don't want it.
                        {

                            string file = "";
                            TreeNode child = new TreeNode();
                            if (b.Contains("self.aspx")) // Sees if it's a file
                            {
                                // Get the file name
                                file = b.Substring(b.IndexOf("self.aspx") + 10 + name.Length + 1);
                                Invoke(new ChangeAdding(ChangeAddingFile), "file: " + file);

                            }
                            if (b.Contains("browse.aspx")) // Sees if it's a folder
                            {
                                // Get the folder name
                                file = b.Substring(b.IndexOf("browse.aspx") + 13 + name.Length);
                                Invoke(new ChangeAdding(ChangeAddingFile), "folder: " + file);

                            }
                            child.Text = file; // Sets the Treenode's text
                            child.Name = b; // Sets the Treenode's name

                            // Adds the child node to the parent node that got passed to the function
                            Invoke(new AddToTree(AddToTreeFunction), child, parentnode);
                            //parentnode.Nodes.Add(child);

                            // Checks whether the link was a folder and if so, we need to recurse through the link
                            // by passing the new link (b) back into this function with the new child node as the parent node
                            AddNode(b, child);

                        } // End big ass if statement
                    } // end if http://cid check
                } // End foreach
            } // End foreach
        }

        /// <summary>
        /// Gets the source code of the given link.
        /// </summary>
        /// <param name="link">The link.</param>
        /// <returns></returns>
        private string GetSource(string link)
        {
            
            string t = "";
            try
            {
                System.IO.Stream st = null;
                System.IO.StreamReader sr = null;

                // make a Web request
                System.Net.WebRequest req = System.Net.WebRequest.Create(link);

                // get the response and read from the result stream
                System.Net.WebResponse resp = req.GetResponse();
                st = resp.GetResponseStream();
                sr = new System.IO.StreamReader(st);

                // read all the text in it
                t = sr.ReadToEnd();
            }
            catch (Exception ex)
            {
                MessageBox.Show("There was an error: " + ex.Message, "Big Fat Hairy Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return t;
        }

        private void btnSignin_Click(object sender, EventArgs e)
        {            
            // Check if a user is already signed in
            if (!oID.IsAuthenticated)
            {
                try
                {
                    // Opens the Sign in window. On successful sign in, oID.Authenticate()
                    // will return true.
                    if (oID.Authenticate())
                    {
                        // Change the label text to show we're signed in and as whom
                        lblInfo.Text = "Signed in as: " + oID.UserName;
                        btnSignIn.Text = "&Sign Out";
                        cid = oID.cId; // This is then used to get the skydrive link for that user.
                        btnCheck_Click(this, e);
                    }
                    else
                    {
                        MessageBox.Show("Authentication failed");
                    }
                }
                catch (WLLogOnException wlex)
                {
                    //Check to see if FlowUrl is defined.
                    if (wlex.FlowUrl != null)
                    {
                        //If FlowUrl is defined, direct user to the web page to correct the error.
                        MessageBox.Show(wlex.ErrorString + "Please go to " + wlex.FlowUrl.AbsoluteUri + "to correct the condition that caused the error", "Big Fat Hairy Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    else
                    {
                        //If FlowUrl is not defined, simply display the ErrorString.
                        MessageBox.Show(wlex.ErrorString);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("There was an error: " + ex.Message, "Big Fat Hairy Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }

            }
            // If a user is signed in, then we are assuming that they want to sign out
            else
            {
                // This closes the active connection to the server
                oID.CloseIdentityHandle();
                // Resets the label and button text
                lblInfo.Text = "Not signed in";
                btnSignIn.Text = "&Sign In";
                btnCheck.Enabled = false;
                linkLabel1.Enabled = false;
                root.Nodes.Clear(); // We're signed out, so we need to clear what's in the tree
            }
        }

        // Delegate and function for adding a node into the tree from the backgroundworker        
        private delegate void AddToTree(TreeNode node, TreeNode parentnode);
        private void AddToTreeFunction(TreeNode node, TreeNode parentnode)
        {
            parentnode.Nodes.Add(node);
        }

        // Delegate and function for expanding the tree nodes so that the public folders are listed.
        private delegate void Expand();
        private void ExpandTrees()
        {
            root.Expand();
            title.Expand();
        }

        // Delegate and function for showing or hiding the spinner to indicate the program is doing something.
        private delegate void ShowHide(bool yn);
        private void ShowHideSpinner(bool yn)
        {
            pictureBox1.Visible = yn;
            btnCheck.Enabled = !yn;
            btnSignIn.Enabled = !yn;
            lblAdding.Visible = yn;
        }

        private delegate void ChangeAdding(string filefolder);
        private void ChangeAddingFile(string filefolder)
        {
            lblAdding.Visible = true;
            lblAdding.Text = "Adding " + filefolder;
        }

        private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            linkLabel1.Enabled = true;
            treeView1.SelectedNode = e.Node;
        }

        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (treeView1.SelectedNode.Nodes.Count == 0)
                DownloadFiles(treeView1.SelectedNode, true, "", false);
            else
            {
                System.Diagnostics.Process process = new System.Diagnostics.Process();
                process.StartInfo.UseShellExecute = true;
                process.StartInfo.FileName = treeView1.SelectedNode.Name;
                process.Start();
            }
        }

        private void treeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Node.Nodes.Count == 0)
            {
                treeView1_NodeMouseClick(this, e);
                linkLabel1_LinkClicked(this, null);
            }
        }

        private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
        {
            linkLabel1.Enabled = true;
            treeView1.SelectedNode = e.Node;
        }

        private void treeView1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TreeNode tn = treeView1.GetNodeAt(e.X, e.Y);
                if (tn == null)
                {
                    return;
                }
                // This is awkward
                if (tn.ImageIndex == 2)
                {
                    treeView1.ContextMenu = null;
                }
                else
                {
                    treeView1.SelectedNode = tn;
                    if (tn.Text == "SkyDrive")
                    {
                        itemOpen.Visible = true;
                        itemDownloadFolder.Visible = false;
                        itemDownload.Visible = false;
                    }
                    else if (tn.Nodes.Count == 0 && tn.Text != "SkyDrive")
                    {
                        itemOpen.Visible = false;
                        itemDownload.Visible = true;
                        itemDownloadFolder.Visible = false;
                    }
                    else
                    {
                        
                        itemOpen.Visible = true;
                        itemDownload.Visible = false;
                        itemDownloadFolder.Visible = true;
                    }
                    treeView1.ContextMenuStrip = contextMenuStrip1;
                }
            }

        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            linkLabel1_LinkClicked(this, null);
        }

        private void DownloadFiles(TreeNode parentnode, bool firstrun, string path, bool allfiles)
        {
            lblAdding.Text = "";
            string folder = "";
            if (firstrun)
            {
                DialogResult result = folderBrowserDialog1.ShowDialog();
                if (result == DialogResult.OK)
                {
                    folder = folderBrowserDialog1.SelectedPath;
                    if (parentnode.Nodes.Count != 0)
                        folder += "\\" + parentnode.Text;
                }
                else return;
            }
            else
            {
                folder = path;
            }
            if (!File.Exists(folder))
            {
                Directory.CreateDirectory(folder);
                lblAdding.Text = "Creating folder " + parentnode.Text;
            }
            if (parentnode.Nodes.Count == 0)
            {
                FileDownloader d = new FileDownloader();
                d.Download(parentnode.Name, folder);
                return;
            }
            foreach (TreeNode child in parentnode.Nodes)
            {
                FileDownloader download = new FileDownloader();
                if (child.Nodes.Count == 0)
                {
                    download.Download(child.Name, folder);
                    lblAdding.Text = "Downloading " + child.Text;
                }
                else
                {
                    if (allfiles)
                    {
                        string newfolder = folder + "\\" + child.Text.Replace("/", "") + "\\";
                        if (!File.Exists(newfolder))
                        {
                            Directory.CreateDirectory(newfolder);
                        }
                        lblAdding.Text = "Creating folder " + child.Text;
                        DownloadFiles(child, false, newfolder, true);
                    }
                }
            }
            lblAdding.Text = "Download complete.";
        }

        private void itemAllFiles_Click(object sender, EventArgs e)
        {
            DownloadFiles(treeView1.SelectedNode, true, "", true);
        }

        private void itemThisFolder_Click(object sender, EventArgs e)
        {
            DownloadFiles(treeView1.SelectedNode, true, "", false);
        }

        private void itemDownload_Click(object sender, EventArgs e)
        {
            linkLabel1_LinkClicked(this, null);
        }
        

    }
}
